wikiversitywikiaorg_ru-20200214-history
Программирование и научные вычисления на языке Python/§3
В следующих двух уроках мы познакомимся с основными конструкциями программирования: циклами while и for, if-else-ветвлениями и функциями, определяемыми самими пользователями. Все это будет использоваться практически в любой программе. Цель нашей следующей программы - развить наш предыдущий пример о двух температурных шкалах и напечатать на экране таблицу с градусами Цельсия в первом столбце и соответствующими им градусами Фаренгейта во втором столбце: -20 -4.0 -15 5.0 -10 14.0 -5 23.0 0 32.0 5 41.0 10 50.0 15 59.0 20 68.0 25 77.0 30 86.0 35 95.0 40 104.0 Наивный подход Конечно, мы можем повторить некоторый код столько раз, сколько нужно с соответствующими значениями переменной, например: C = -20; F = 9.0/5*C + 32; print C, F C = -15; F = 9.0/5*C + 32; print C, F C = -10; F = 9.0/5*C + 32; print C, F C = -5; F = 9.0/5*C + 32; print C, F C = 0; F = 9.0/5*C + 32; print C, F C = 5; F = 9.0/5*C + 32; print C, F C = 10; F = 9.0/5*C + 32; print C, F C = 15; F = 9.0/5*C + 32; print C, F C = 20; F = 9.0/5*C + 32; print C, F C = 25; F = 9.0/5*C + 32; print C, F C = 30; F = 9.0/5*C + 32; print C, F C = 35; F = 9.0/5*C + 32; print C, F C = 40; F = 9.0/5*C + 32; print C, F и после запуска получить: -20 -4.0 -15 5.0 -10 14.0 -5 23.0 0 32.0 5 41.0 10 50.0 15 59.0 20 68.0 25 77.0 30 86.0 35 95.0 40 104.0 Форматирование выглядит криво, но его собственно как такого и не осуществлялось и ситуацию легко изменить с помощью printf-форматирования, что мы покажем далее. Но основная проблема заключается в другом - в том, что нам приходится множество раз повторять идентичные выражения, в то время как компьютер создан как раз для автоматизации скучной работы. В общем, нам пора узнать о циклах (loops) и их двух реализациях в Python: циклах while и циклах for. Цикл while Цикл while используется для того, чтобы повторять заданные наборы инструкций столько раз, сколько условие сохраняет истину. Проиллюстрируем это на примере. Задача состоит в создании строк таблицы из значений C и F. Первое значение C равно -20 и с шагом 5 оно движется в сторону роста С пока С ≤ 40. Для каждого C считается соответствующее значение F и так записываются две температуры. Записать все сказанное словами в виде кода можно, например, так: print '------------------' # верхняя линия таблицы C = -20 # начальное значение C dC = 5 # инкремент для C в цикле while C <= 40: # заголовок цикла с условием F = (9.0/5)*C + 32 # 1ая инструкция цикла print C, F # 2ая инструкция цикла C = C + dC # 3ья инструкция цикла print '------------------' # нижняя линия (после цикла) Уже сейчас стоит рассмотреть в прямом смысле слова незаметную, но очень важную часть языка Python - отступы. Как мы видим, после того как мы объявили блок while, задали условие и поставили двоеточие, весь следующий текст записан с определенным отступом. Ширина этого отступа, которой следует придерживаться, равна четырем пробелам. Многие начинающие программисты Python забывают ставить двоеточие в конце строки с while - это двоеточие существенно и показывает, что начинается блок инструкций цикла. Немногим позже мы увидим, что имеется множество схожих конструкций, открывающихся двоеточием. Программисты должны полностью понимать, что происходит в программе и быть готовы дать тот же ответ, что и программа еще до того как она запущена. Давайте обсудим, что происходит в этом коде. Вначале, мы определяем начальное значение температуры в градусах Цельсия: С = -20. Также мы определяем инкремент dC, чтобы добавлять его к C в цикле. Затем мы начинаем цикл с условием С ≤ 40. В первый момент C = -20, -20 ≤ 40, условие выполняется. Пока условие выполняется, цикл выполняет заложенные в нем действия. То есть считает соответствующее F, печатает две температуры и добавляет dC к C. После этого совершается второй проход по циклу. Вначале мы проверяем правило цикла: теперь С = -15, но выражение все еще остается истинным: -15 ≤ 40. Поэтому выполняются все те же инструкции, следующие после двоеточия. И так до тех пор пока выражение С ≤ 40 истинно. А ложным оно может стать лишь тогда, когда С > 40. Немного об инкременте Новичков в программировании иногда смущает следующее выражение: C = C + dC Эта строка выглядит некорректно с точки зрения математики, но как программный код не вызывает никаких нареканий, поскольку вначале происходят какие-то действия с правой стороны от знака равенства, а потом уже они присваиваются переменной слева. В нашем случае складываются C и dC, два объекта типа int. Эта операция дает тоже объект типа int и уже он присваивается переменной C. А объект, связанный с этим именем ранее в памяти освобождается, поскольку на него более не ссылается ни одно имя. Поскольку подобные инкременту операции часто встречаются в программировании, существует более короткий способ записи таких операций, о нем будет неплохо узнать: C += dC # то же, что C = C + dC C -= dC # то же, что C = C - dC C *= dC # то же, что C = C * dC C /= dC # то же, что C = C / dC Логические выражения В нашем примере с циклом while мы использовали условие записанное как C <= 40, которое принимает значения True (истина) или False (ложь). В зависимости от задачи, вы можете использовать и другие условия проверки: C 40 # C равно 40 C != 40 # C не равно 40 C <> 40 # второй вариант записи C не равно 40 C >= 40 # C больше или равно 40 C > 40 # C больше 40 C < 40 # C меньше 40 Сравниваться могут не только числа. Возможно любое логическое (булево) сравнение. Также перед выражением может быть поставлено слово not для изменения True на False и наоборот. Также возможно комбинировать булеву логику, как например: while x > 0 and y <= 1: print x, y Здесь, только пока оба выражения истинны, будут печататься x, y. В любом обратном случае цикл завершается. Эта функция называется "логическое и". Более "мягкий" подход - "логическое или" (or), для которого выражение принимает истинное значение, когда хотя бы одно из сравнений дает истину: while x > 0 or y <= 1: print x, y Вот несколько примеров в интерактивном режиме, чтобы потренироваться на логических выражениях и быть с ними более аккуратными, чем это бывает обычно: >>> x = 0; y = 1.2 >>> x >= 0 and y < 1 False >>> x >= 0 or y < 1 True >>> x > 0 or y > 1 True >>> x > 0 or not y > 1 False >>> -1 < x <= 0 # -1 < x and x <= 0 True >>> not (x > 0 or y > 0) False Списки До этого момента все наши переменные могли содержать только одно число, будь оно хоть целым (int), хоть с плавающей точкой (float), хоть комплексным (complex) объектом. Но иногда числа естественным образом группируются. Например, градусы шкалы Цельсия в нашем первом столбце вполне представляют из себя группу. Для групп в Python есть специальный тип объектов - list (список), который позволяет организовывать эти группы в последовательности. При этом, с помощью списков мы можем работать как с группой целиком, так и с членами этой группы по отдельности. При этом список может состоять из объектов разного типа, в том числе и из самих списков, что как мы увидим далее, может быть очень удобным. Для того чтобы создать список чисел из первого столбца нашей таблицы, нам следует записать их через запятую в квадратных скобках, вот так: C = -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40 Есть, кстати, и более элегантный способ, но не будем забегать вперед. Теперь наша переменная C ссылается на объект типа list, который содержит последовательность 13 элементов. Все элементы в этом случае являются int-объектами. Любой элемент в списке связан со своим индексом, который определяет позицию элемента в списке. Первый элемент имеет индекс 0, второй элемент - индекс 1 и так далее. Таким образом в списке C имеется 13 элементов с индексами от 0 до 12. Для того, чтобы вызвать какой-то элемент списка, достаточно вызвать его по индексу, например C3, если мы хотим вызывать -5. Элементы в списках можно удалять, добавлять, изменять, вставлять и так далее. Делается это с помощью методов. Обратите внимание на эту концепцию, она встретится нам не раз. Методы определяются через точку. Например метод append для нашего списка C запишется как C.append(v), и этот метод добавит элемент v'' в конец списка С. А метод C.insert(i,v) вставит новый элемент ''v на позицию i''. В интерактивном режиме выглядит это так: >>> C = -5, 0, 5, 10, 15, 20, 25, 30 # создаем список >>> C.append(35) # добавляем в конец новый элемент >>> C # смотрим список -5, 0, 5, 10, 15, 20, 25, 30, 35 >>> C = C + 45 # способ соединения списков >>> C -5, 0, 5, 10, 15, 20, 25, 30, 35, 40, 45 Но заметим, что, как мы уже знаем, в последнем случае был создан новый список. Сами элементы можно было вставить куда угодно, как мы сейчас сделаем с помощью метода C.insert(): >>> C.insert(0, -15) # вставить новый элемент -15 с индексом 0 >>> C -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40, 45 С помощью del Ci мы можем удалять любые элементы, с помощью len© считать число элементов. Метод C.index(v) позволяет определить под каким индексом расположен интересующий нас элемент. >>> del C2 # удалить третий элемент >>> C -10, 0, 5, 10, 15, 20, 25, 30, 35, 40, 45 >>> del C2 # удалить то, что теперь третий элемент >>> C -10, 5, 10, 15, 20, 25, 30, 35, 40, 45 >>> len© # длина списка 11 >>> C.index(10) 3 Для того, чтобы просто узнать имеется ли какой-то элемент в списке используется команда in: >>> 10 in C # есть ли среди элементов C число 10? True Кроме всего прочего Python поддерживат отрицательные индексы. Например, в списке C последний элемент может быть обозначен как С-1, предпоследний как C-2 и так далее: >>> C-1 45 >>> C-2 40 Также с помощью списков можно сделать компактное присвоение элементов переменным. Например: >>> somelist = 'book.log', 'book.pdf' >>> texfile, logfile, pdf = somelist >>> texfile 'book.tex' >>>' logfile 'book.log' >>> ' pdf 'book.pdf' При этом, конечно, число переменных слева от знака равенства должно равняться числу элементов списка, иначе случится ошибка. Заметим, напоследок, различие в ''функциях и методах. Например, C.append(e) является методом, а len© - функцией. Как можно заметить просто визуально, методы записываются через точку. Эта их "префиксность" показывает, что они относятся только к объекту такого типа и производятся только над конкретным экземпляром. Функции же как правило, могут быть использованы для различных типов объектов. При этом каких-то особенных различий в действиях нет. То есть методы по сути те же функции, но записываемые привязано к объекту. Цикл for Обычно действия, которые мы производим над одним элементом какой-то последовательности, мы хотели бы произвести и над всеми элементами. Для этого в языках программирования имеется специальная конструкция, называемая цикл for (for loop). Давайте сразу начнем с примера: degrees = 10, 20, 40, 100 for C in degrees: print 'list element: ', C print 'The degrees list has', len(degrees), 'elements' Предложение for C in degrees создает цикл для всех элементов списка degrees. При каждом проходе по циклу переменная C действует как курсор - берет соответствующий элемент из списка degrees, начиная с degrees0 и подставляет в инструкцию цикла. Потом то же делает с degrees1 и так далее до degreesn-1, где n - число элементов в списке, которое мы можем определить функцией len(degrees), что и делаем по окончании цикла. Заголовок цикла for аналогично заголовку цикла while заканчивается двоеточием, после которого следует тело цикла с отступами в 4 пробела. В данном случае в теле цикла всего одна инструкция. В итоге на экране видим: list element: 0 list element: 10 list element: 20 list element: 40 list element: 100 The degrees list has 5 elements Делаем таблицу Теперь наши знания о циклах и списках помогут написать программу, выводящую требуемую таблицу. При этом нам будет понятно, что происходит "под капотом". Cdegrees = -15, 10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40 for C in Cdegrees: F = (9.0 / 5) * C + 32 print C, F Инструкция print C, F просто печатает значения C и F, а мы бы хотели создать форматированный вывод. Тогда, используя printf-форматирование, запишем окончательный вариант: Cdegrees = -15, 10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40 print ' C F' for C in Cdegrees: F = (9.0 / 5) * C + 32 print '%5d %5.1f' % (C, F) Альтернативные возможности Итак, наша программа делает то, чего мы хотели в самом начале. Для любой проблемы обычно существует несколько решений. Здесь мы узнаем о нескольких других возможных конструкциях. for как while Любой for цикл может быть записан как цикл while. В общем случае for-цикл: for element in somelist: может быть преобразован в while: index = 0 while index < len(somelist): element = somelistindex index += 1 В том числе и наш пример: Cdegrees = -15, 10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40 index = 0 print ' C F' while index < len(Cdegrees): C = Cdegreesindex F = (9.0 / 5) * C + 32 print '%5d %5.1f' % (C, F) index += 1 Столбцы в виде списков Небольшое изменение нашей программы позволит записывать уже не одну, а обе шкалы в виде списка. Для шкалы Фаренгейта мы можем создать пустой список, в который по мере проходов по циклу будут добавляться соответствующие значения: Cdegrees = -15, 10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40 Fdegrees = [] for C in Cdegrees: F = (9.0 / 5) * C + 32 Fdegrees.append(F) И если мы после этого распечатаем список Fdegrees, то получим: 5.0, 14.0, 23.0, 32.0, 41.0, 50.0, 59.0, 68.0, 77.0, 86.0, 95.0, 104.0 Циклы со списком индексов Вместо цикла for по заданным элементам списка мы можем использовать цикл for с генерированным списком индексов. Для этого в Python есть функция range, возвращающая список по порядку идущих чисел: * range(n) возвращает список 1, 2, ..., n-1 * range(start, stop, step) возвращает список start+step, start+2*step, ..., start+(n-1)*step, то есть число stop не включается в список, как и ранее. Например, range(2, 8, 3) дает 5; range(1, 11, 2) возвращает 3, 5, 7, 9. * range(start, stop) то же самое, что range(start, stop, 1). Таким образом, рассмотренный цикл for может быть записан и так: Cdegrees = range(-20, 45, 5) Fdegrees = 0.0*len(Cdegrees) # list of 0.0 values for i in range(len(Cdegrees)): Fdegreesi = (9.0/5)*Cdegreesi + 32 Заметим, что в этом примере мы инициализировали Fdegrees списком длиной len(Cdegrees), в котором все элементы равны нулю. Иначе, если бы мы создали пустой список Fdegrees = [], обращение Fdegreesi дало бы ошибку, поскольку список пуст, и мест нет. Еще о списках Изменение Сейчас мы могли бы предложить два способа того как можно изменять элементы списка, например: for c in Cdegrees: c += 5 и этот цикл ничего не изменяет, в то время как for i in range(len(Cdegrees)): Cdegreesi += 5 работает исправно. Что же не так с первым циклом? Проблема в том, что с'' в первом случае это всего лишь переменная, которая ссылается на элемент списка, и когда мы к этой переменной применяем выражение c += 5, мы изменяем переменную, но не цикл. То есть, как говорилось ранее, можно представлять такого рода переменные как курсоры, а действие над указателем не изменяет объект. Показать, что происходит, можно на примере первых двух проходов по циклу: c = Cdegrees0 c += 5 c = Cdegrees1 c += 5 Как видно отсюда, переменная ''c может быть использована только для чтения элементов, но не для их изменения. Только присваивание вида: Cdegreesi = ... изменяет элемент списка. Существует возможность совмещения действий с индексами и элементами, например, цикл for i, c in enumerate(Cdegrees): Cdegreesi = c + 5 добавляет число 5 к каждому элементу списка. Генерация Поскольку прохождение по списку и создание соответствующего нового списка является часто встречаемой задачей, в Python имеется компактный синтаксис для решения этой проблемы, который часто называют генерацией списка. В общем виде это выглядит так: newlist = for e in list где E(e) представляет собой выражение с элементом e. Вот три примера: Cdegrees = + i*0.5 for i in range(n) Fdegrees = + 32 for C in Cdegrees C_plus_5 = for C in Cdegrees Двойной проход (zip) Мы можем использовать списки Cdegrees и Fdegrees для того, чтобы получить таблицу. Для этого мы должны пройти по обеим последовательностям. Конструкция for element in list в этом случае уже не очень удобна, поскольку она извлекает элементы только из одного списка. Поскольку мы знаем, что в каждом списке одно и то же число элементов, можно поступить например так: for i in range(len(Cdegrees)): print '%5d %5.1f' % (Cdegreesi, Fdegreesi) Такая задача - о том как одновременно пройти по двум и более спискам встречается довольно часто. В Python для этого есть свое элегантное решение: for e1, e2, e3, ... in zip(list1, list2, list3, ...): # работаем с e1 из list1, e2 из list2 и т.д. Функция zip собирает из n списков (list1, list2, list3, ...) один список из n-последовательностей (кортежей). Теперь каждая переменная e1, e2, e3 и т. д. служит своеобразным курсором для своей последовательности и двигаются параллельно. Цикл останавливается как только достигается конец самого короткого списка. Разъясним применение zip на нашем примере с таблицей из двух списков Cdegrees и Fdegrees: for C, F in zip(Cdegrees, Fdegrees): print '%5d %5.1f' % (C, F) Заметно, что этот подход более естественен, чем конструкции вида for i in range(len(Cdegrees)). Вложение В нашей таблице мы используем отдельный список для каждого столбца. Если нам будет нужно n столбцов, то потребуется и n списков. Однако, обычно мы представляем себе какую-то таблицу как единое целое, а не как ряд столбцов или строк. Значит, будет и более естественно использовать единый аргумент для всей таблицы. Для этого можно использовать вложенные списки: Cdegrees = range(-20, 41, 5) # -20, -15, ..., 35, 40 Fdegrees = + 32 for C in Cdegrees table = Fdegrees Стоит заметить, что индексация к определенному элементу организуется также. То есть, чтобы обратиться к первому списку, мы напишем table0, а чтобы обратиться к третьему элементу этого списка, мы приставляем следующий индекс table02. Все это органично входит в концепцию объектов - table0 это тоже объект и его можно индексировать как любой объект, содержащий последовательность элементов. Если идти дальше, то с помощью zip можно организовать и более естественное заполнение таблицы: table = [] for C, F in zip(Cdegrees, Fdegrees): table.append(F) а с генерацией списков можно получить совсем короткое: table = [F for C, F in zip(Cdegrees, Fdegrees)] Срезы В Python имеется прекрасный синтаксис для вырезания частей списков, который называется срез (sublist или slice). Срезы никогда не изменяют исходный список, создавая новый. Теперь примеры. Ai: это срез, который вырезает список из исходного списка A, начиная с индекса i до конца: >>> A = 3.5, 8, 10 >>> A2: 10 Ai:j вырезает начиная с элемента i до j-1: >>> A1:3 8 A:i вырежет из старого списка новый список, забрав все элементы вплоть до i: >>> A:3 3.5, 8 A1:-1 вырезает все элементы кроме первого и последнего (заметьте, что можно использовать отрицательную индексацию), а A: копирует весь список: >>> A1:-1 8 >>> A: 3.5, 8, 10 Покажем, что срезы всего лишь копии. Доказательство 1: >>> l1 = 4, 3 >>> l2 = l1:-1 >>> l2 4 >>> l10 = 100 >>> l1 4, 3 >>> l2 4 Доказательство 2: >>> B = A: >>> C = A >>> B A True >>> B is A False >>> C is A True Равенство, которое записывается , как мы помним является сравнением значений элементов - если они равны, то возвращается True. Выражение is сравнивает сами объекты, если переменные ссылаются на один и тот же объект в памяти (а не на копию), то возвращается True, в обратном случае False. Зачем же нужны срезы? Покажем на примере нашей "табличной" программы: >>> for C, F in tableCdegrees.index(10):Cdegrees.index(35): ... print ’%5.0f %5.1f’ % (C, F) ... 10 50.0 15 59.0 20 68.0 25 77.0 30 86.0 Помните, что всякий раз, как вы видите пример, вы должны подумать, понимаете ли вы как он работает. Больше примеров Кортежи Кортежи (tuples) очень похожи на списки, но кортежи нельзя изменять. В отличие от списков, кортежи записываются в круглых скобках: >>> t = (2, 4, 6, ’temp.pdf’) Но во многих случаях скобки можно отбросить: >>> t = 2, 4, 6, ’temp.pdf’ >>> for element in ’myfile.txt’, ’yourfile.txt’, ’herfile.txt’: ... print element, ... myfile.txt yourfile.txt herfile.txt Большинство возможностей из списков доступно и в кортежах: >>> t = t + (-1.0, -2.0) >>> t (2, 4, 6, ’temp.pdf’, -1.0, -2.0) >>> t1 4 >>> t2: (6, ’temp.pdf’, -1.0, -2.0) >>> 6 in t True Но любая попытка изменить содержание кортежа приводит к неудаче: >>> t1 = -1 ... TypeError: object does not support item assignment >>> t.append(0) ... AttributeError: ’tuple’ object has no attribute ’append’ >>> del t1 ... TypeError: object doesn’t support item deletion Возникает вопрос — зачем нужны кортежи, если списки могут делать большее? Во-первых, из того, что кортеж нельзя изменить, вытекает что их можно использовать в таких случаях — когда нельзя изменять какую-то последовательность. Во-вторых, работа с кортежами происходит быстрее, чем со списками — для неизменяемых объектов требуется меньше памяти. В-третьих, кортежи часто используются в Python в качестве ключей в таком типе объектов как словари, где не могут использоваться списки. Чему мы научились Это очень важный урок, в котором мы узнали о двух важных областях: такой общевостребованной части программирования как циклы и об особенных объектах Python как последовательности — списки и кортежи. Без этих вещей не обходится практически ни одна программа на Python. Поэтому будет неплохо потренироваться на упражнениях. Упражнения Категория:Программирование и научные вычисления на языке Python